package com.magenta.maxoptra.geocoding.geobase;

import com.magenta.maxoptra.geobase.GeoBaseConstants;
import com.magenta.maxoptra.geocoding.geobase.webservice.*;
import com.magenta.maxoptra.gis.*;
import com.magenta.mrs.basegeocoding.BaseGeocoding;
import com.magenta.mrs.util.GeoInformationClient;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;

import java.net.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.magenta.maxoptra.gis.MapProviderType.geobase;
import static com.magenta.maxoptra.gis.VehicleType.*;

/**
 * Created with IntelliJ IDEA.
 * User: kuznetsovd
 * Date: 14.11.12
 * Time: 17:01
 * To change this template use File | Settings | File Templates.
 */
public abstract class AbstractProviderGeoBase extends BaseGeocoding {

    protected static final ObjectFactory FACTORY = new ObjectFactory();

    private static Map<VehicleType, Integer> geoBaseVehicleTypes = new ConcurrentHashMap<VehicleType, Integer>();
    static {
        geoBaseVehicleTypes.put(Default, 0);
        geoBaseVehicleTypes.put(Auto, 1);
        geoBaseVehicleTypes.put(Bus, 2);
        geoBaseVehicleTypes.put(Taxi, 4);
        geoBaseVehicleTypes.put(CarPool, 8);
        geoBaseVehicleTypes.put(Pedestrian, 16);
        geoBaseVehicleTypes.put(Emergency, 256);
        geoBaseVehicleTypes.put(StraightTruck, 512);
        geoBaseVehicleTypes.put(TractorSemiTrailer, 1024);
        geoBaseVehicleTypes.put(StandardDouble, 2048);
        geoBaseVehicleTypes.put(IntermediateDouble, 4096);
        geoBaseVehicleTypes.put(LongDouble, 8192);
        geoBaseVehicleTypes.put(Triple, 16384);
        geoBaseVehicleTypes.put(OtherLCV, 32768);
        geoBaseVehicleTypes.put(Unknown, 65536);
        geoBaseVehicleTypes.put(Truck, 65024);
        geoBaseVehicleTypes.put(All, 65295);
        geoBaseVehicleTypes.put(AllButEmergency, 65039);
    }

    /**
     * В данном случае используется java native клиент (не от Apache) для запроса данных по URL,
     * поэтому используется явное определение свойств прокси
     * (т.е. не по стандартной схеме, которая используется в приложении)
     */
    protected java.net.Proxy proxy;

    protected ArrayOfDouble getRoutingProfileSpeeds() {
        String configValue = this.client.getProperties().get(GisParameter.routing_profile.withProvider(getProviderName()));

        if (configValue == null || StringUtils.isBlank(configValue)) {
            return null;
        }

        String[] arrayValue = configValue.split(", *", 48);
        ArrayOfDouble arrayOfDouble = FACTORY.createArrayOfDouble();
        List<Double> result = arrayOfDouble.getDouble();

        try {
            for (int i = 0; i < arrayValue.length; i++) {
                result.add(Double.parseDouble(arrayValue[i]));
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
            return null;
        }

        return arrayOfDouble;
    }

    protected ArrayOfWsLatLon getRestrictedRoads() {
        String configValue = getGisStringProperty(GisParameter.restricted_roads);

        if (configValue == null || StringUtils.isBlank(configValue)) {
            return null;
        }

        String[] arrayValue = configValue.split(" *\\| *");
        ArrayOfWsLatLon arrayOfWsLatLon = FACTORY.createArrayOfWsLatLon();
        List<WsLatLon> result = arrayOfWsLatLon.getWsLatLon();

        try {
            for (String stringCoordinates: arrayValue) {
                String[] stringLatLon = stringCoordinates.split(", *", 2);
                result.add(new WsLatLon(
                        Double.parseDouble(stringLatLon[0]),
                        Double.parseDouble(stringLatLon[1])
                ));
            }
        }
        catch (NumberFormatException e) {
            e.printStackTrace();
            return null;
        }
        catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            return null;
        }

        return arrayOfWsLatLon;
    }

    protected RouteSettings getRouteSettings() {
        return getRouteSettings(getDefaultVehicleType());
    }

    protected RouteSettings getRouteSettings(VehicleType vehicleType) {
        final RouteSettings routeSettings = FACTORY.createRouteSettings();
        routeSettings.setSpeeds(getRoutingProfileSpeeds());
        routeSettings.setVehicleType(geoBaseVehicleTypes.get(vehicleType));
        routeSettings.setRestrictedRoads(getRestrictedRoads());
        routeSettings.setUseTollRoads(Boolean.valueOf(getGisStringProperty(GisParameter.use_toll_roads)));
        routeSettings.setUseShortestRoute(getGisBooleanProperty(GisParameter.use_shortest_route));
        return routeSettings;
    }

    protected GeoAddress createGeoBaseAddress(SurrogateGeocodeAddress geoBaseAddress) {
        GeoAddress resultAddress = new GeoAddress();
        resultAddress.setCity(geoBaseAddress.getCity());
        resultAddress.setCountry(geoBaseAddress.getCountry().name());
        resultAddress.setZip(geoBaseAddress.getPostalCode());
        resultAddress.setStreet(StringUtils.isBlank(geoBaseAddress.getLine1()) ? geoBaseAddress.getStreet() : geoBaseAddress.getLine1());
        resultAddress.setLatitude(geoBaseAddress.getLatitude());
        resultAddress.setLongitude(geoBaseAddress.getLongitude());
        return resultAddress;
    }

    protected Country getCountry(GeoProviderSettings settings) throws GeoException {
        String region = settings.getGisParam(GisParameter.region.withProvider(getProviderName()));
        Country country = null;
        if (region == null) throw new GeoException("Need to set region in account settings");
        try {
            country = Country.fromValue(region);
        } catch (Exception e) {
            throw new GeoException("Unknown region '" + region + "' in account settings");
        }
        return country;
    }


    protected GeoAddress buildGeoAddress(AddressWithStreetLink addressLink, GeographicCoordinates geoCoordinates) {
        Address address = addressLink.getAddress();
        GeoAddress result = new GeoAddress();
        result.setCity(address.getCity());
        result.setCountry(address.getCountry().name());
        result.setZip(address.getPostalCode());
        result.setCounty(getGeoAddressCounty(address));
        result.setInfo(getGeoAddressInfo(address));
        result.setState(address.getRegion());
        result.setSpeedLimit(addressLink.getSpeedLimit());
        result.setHouse(new Integer(address.getNumber()).toString());
        if (address.getNames() != null && address.getNames().getString().size() > 0) {
            result.setStreet(address.getNames().getString().get(0));
        }

        //TODO: what to set for list of GeographicCoordinates ?
        if (geoCoordinates != null) {
            /**
             * NOTE: Telogis Geobase сервис возвращает адрес, но без координат. Так как поиск производится по
             * координатам, то их же вручную проставляем в результирующем адресе.
             */
            result.setLatitude(geoCoordinates.getLatitude());
            result.setLongitude(geoCoordinates.getLongitude());
        }
        return result;
    }

    protected String getGeoAddressCounty(Address address) {
        if (address.getSubRegions() != null) {
            StringBuilder builder = new StringBuilder();
            List<String> regionNames = address.getSubRegions().getString();
            int commas = regionNames.size() - 1;
            for (String subRegionName : regionNames) {
                builder.append(subRegionName);
                if (commas-- > 0) {
                    builder.append(',').append(' ');
                }
            }
            return builder.toString();
        }
        return null;
    }

    protected String getGeoAddressInfo(Address address) {
        if (address.getExtendedNameInfo() != null) {
            StringBuilder builder = new StringBuilder();
            List<NameInfo> nameInfos = address.getExtendedNameInfo().getNameInfo();
            int commas = nameInfos.size() - 1;
            for (NameInfo nameInfo : nameInfos) {
                builder.append(nameInfo.getName());
                if (commas-- > 0) {
                    builder.append(',').append(' ');
                }
            }
            return builder.toString();
        }
        return null;
    }

    @Override
    protected GisParameters getProviderParams() {
        return client.getProperties();
    }


    @Override
    protected List<GeoAddress> doFindAddressByKeyword(GeoProviderSettings settings, String keyword, int i, List accuracies)
            throws GeoException {
        return null;
    }

    @Override
    protected boolean accuracyIsAccurateEnough(String accuracyThreshold, Object accuracy) {
        return false;
    }

    protected URL getURL() {
        GisParameters gisParams = getProviderParams();
        if (gisParams != null) {
            String urlStr = gisParams.get(GeoBaseConstants.GEOBASE_WSDL_LOCATION);
            if (urlStr == null) {
                throw new IllegalStateException("Parameter " + GeoBaseConstants.GEOBASE_WSDL_LOCATION +
                        " is not set for " + geobase.getDescription());
            }

            try {
                URL url = new URL(urlStr);
                return url;
            } catch (MalformedURLException e) {
                throw new IllegalStateException("URL is not correct: '" + urlStr + "'");
            }
        } else {
            throw new IllegalStateException("Parameters are not set for " + geobase.getDescription());
        }
    }

    @Override
    public void setClient(GeoInformationClient client) {
        this.client = client;
        HttpHost httpProxy = (HttpHost) client.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY);
        if (httpProxy != null) {
            String proxyHost = httpProxy.getHostName();
            int proxyPort = httpProxy.getPort();
            final Credentials credentials = ((DefaultHttpClient) client).getCredentialsProvider().getCredentials(
                    new AuthScope(proxyHost, proxyPort));

            SocketAddress socketAddress = new InetSocketAddress(proxyHost, proxyPort);
            this.proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddress);

            Authenticator.setDefault(new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    PasswordAuthentication p = new PasswordAuthentication(credentials.getUserPrincipal().getName(),
                            credentials.getPassword().toCharArray());
                    return p;
                }
            });
        }
    }


}
